Camera 2 的简单使用

一、简单介绍

  • Camera2 是 Android 5.0 后,Google 官方推出新的相机 API 。支持各种新的特性,什么光学防抖啊,相位对焦啊,都提供了支持。
    更重要的是,允许程序调整相机的对焦模式曝光模式快门,还支持 RAW 照片输出。
  • Camera2 使用的请求方式是类似管道的方式,其中主要用到了两个类,CameraRequestCameraCaptureSession。通过建立管道,获取 session , 然后就可以使用
    session 来发请求了。

二、主要的类

image

CameraManager

  • 摄像头管理器。这是一个全新的系统管理器,专门用来检测系统摄像头,打开摄像头,可通 过调用 ContextgetSystemService(Context.CAMERA_SERVICE)来获取。
  • getCameraCharacteristics(String cameraId)
    获取指定摄像头的相关特性。
  • openCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler)
    打开摄像头

CameraCharacteristics

  • 摄像头特性。通过CameraManger获取,存储着摄像头所支持的各种特性。

CameraDevice

  • 摄像头设备。该类的功能类似于早期的Camera类。

CameraCaptureSession

  • 这个类就有点像网络请求中的 session ,一个摄像头对象(CameraDevice)最多只能同时存在一个CameraCaptureSession对象,一旦建立起新的连接,旧的连接就会断开,旧的 session对象就会失效。

  • 该对象可以通过CameraDevice调用
    createCaptureSession(List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler)来获取。

    接收三个参数

    • outputs : 一个 Surface 类型 的 List 对象,用来作为输出的集合。
      有一点要注意的,CameraRequest.BuilderaddTarget(Surface target)方法也是传入一个 Surface 对象作为输出对象。那么这两者有什么区别呢?
      在官方的文档中有提到一点,target 必须为 outputs 的子集,也就是说 outputs 包含 target 对象。就我的理解而言,outputs 是所有输出的集合,而 target 是某次请求的目标。

    • 回调函数,有两个方法。可获取到session

    • 用来指定在某个 Handler 执行回调函数。

  • capture(CaptureRequest request, CaptureCallback listener, Handler handler)
    用来请求捕获一张图片。其中第二个参数 CatpureCallback 是一个静态抽象类,有回调周期的各种回调,只需要选择需要的进行重新就行。

  • setRepeatingRequest(CaptureRequest request, CaptureCallback listener, Handler handler)
    用来请求连续的图像,直到调用stopRepeating()才会停止。一般用来进行预览画面。

CameraRequest

  • 向相机发送的请求。一般通过CameraRequest.Builder来创建。

CameraRequest.Builder

  • addTarget(Surface outputTarget)
    请求的目标,也就是获取到的图像,传给谁。
  • set(Key<T> key, T value)
    设置参数,以 key - value 的形式设置参数。
  • build()
    创建 CameraRequest 。

使用流程

权限

使用到了下面几个权限

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />

分析

在 Camera2 的 API 中,已经不像 Camera 一样直接提供数据了,而是把数据给到了 Surface 。那么,这就尴尬了。要怎么从 Surface 中获取图片数据呢。

其实在预览的时候,我们根本就不需要管数据,因为 Camera2 会直接把数据塞给 Surface 。

我们知道,Surface 其实是一块原始的图像数据,所有我们在屏幕上看到的东西,都存储在 Surface 中,一个 Surface 一般会有两个缓冲区。WindowManager 会为每一个 window 创建 Surface 。而通过 lockCanvas() 获取到 Canvas , 然后 View 就可以在 Canvas 上面画东西了。

但是,如果要拍照呢?那就用到了 ImageReader 这个类了。

ImageReader

  • 这个类从 Android 4.4 就引进了,但是很少用,直到 5.0 的 Camera2 出现,ImageReader 的使用频率才提高了。

  • ImageReader 是什么东西呢?看看官方的解释

    The ImageReader class allows direct application access to image data rendered into a Surface

    其实就是一个可以直接操作 Surface 的类。

  • 那就很舒服了,我们可以通过 ImageReader 来获取图像数据了。

流程

  • 获取 CameraManager。
  • 获取 CameraCharacteristics 选取要使用的摄像头。 根据获取到的数据,初始化和配置 TextureView (当然,也可以使用 SurfaceView 等其他类似的 View) 和 ImageReader。
  • 创建和配置 CameraRequest , CameraCaptureSession
  • 发送请求
  • 回调中进行处理

代码

这里先贴两个官方的 Demo ,写得很好,对各种情况都进行了处理。

https://github.com/googlesamples/android-Camera2Basic
https://github.com/googlesamples/android-Camera2Video

好了,接下来对 官方 Demo 进行一波解析

首先,官方整一个过程全都是写在 Fragment 里面。不知道为什么要写在 Fragment ,可能有什么很深层的东西要表达吧,又或者官方觉得,Fragment 的生命周期相对 Activity 来说更复杂一点吧,又或者,官方觉得在 Fragment 上使用的会比较多吧。

我居然YY了这么多。。。

好了,正式看代码

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_camera2_basic, container, false);
    }

    @Override
    public void onViewCreated(final View view, Bundle savedInstanceState) {
        view.findViewById(R.id.picture).setOnClickListener(this);
        view.findViewById(R.id.info).setOnClickListener(this);
        mTextureView = (AutoFitTextureView) view.findViewById(R.id.texture);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mFile = new File(getActivity().getExternalFilesDir(null), "pic.jpg");
    }

    @Override
    public void onResume() {
        super.onResume();
        startBackgroundThread();

        // When the screen is turned off and turned back on, the SurfaceTexture is already
        // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
        // a camera and start preview from here (otherwise, we wait until the surface is ready in
        // the SurfaceTextureListener).
        if (mTextureView.isAvailable()) {
            openCamera(mTextureView.getWidth(), mTextureView.getHeight());
        } else {
            mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
        }
    }

onActivityCreated 中创建了一个文件,是到时候用来存拍照的图片的。

onResume 中 启动了一个后台线程,然后做了一个判断,如果 mTextureView 可见就打开摄像头开始预览,如果不可见,就设置一个可见时的监听。还写了一段注释,大概是说,当屏幕关闭,或者被放到后台,SurfaceTexture 已经是可见的了,但是,onSurfaceTextureAvailable 不会再回调一次。在这个例子中,我们可以打开摄像头,直接进行预览,否则,我们就要等 SurfaceTextureListener 回调的时候再打开摄像头进行预览。

/**
 * Starts a background thread and its {@link Handler}.
 * 开启后台线程
 */
private void startBackgroundThread() {
    mBackgroundThread = new HandlerThread("CameraBackground");
    mBackgroundThread.start();
    mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}

先创建了一个 HandlerThread ,这个东西和 Thread 有什么区别呢?这个其实是一个自带 Looper 的 Thread 。然后通过 HandlerThread 创建了一个 Handler 。再说一点,HandlerThread 要 getLooper 前要先跑起来,也就是要先调 start 。

后面就不扯了,直接上代码吧。

由于官方的注释都是英文,看起来也不好理解,于是在简书上找到了一篇文章。

地址如下:

http://www.jianshu.com/p/2d3ee80198e7

# Android 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×